generic-mcp-server

by v4lheru
Verified
# Instructions for Creating an MCP Server This document provides comprehensive guidance for Language Models (LLMs) on how to create a Model Context Protocol (MCP) server. These instructions emphasize modularity, organization, and maintainability as core requirements. ## Core Requirements When creating an MCP server, you **MUST** adhere to these fundamental principles: 1. **Modularity**: Break down functionality into logical, self-contained modules with clear responsibilities. 2. **Smaller Files**: Keep individual files focused and concise (ideally under 300 lines) to reduce cognitive load and make them easier to ingest. 3. **Clear Organization**: Follow a consistent directory structure that separates concerns. 4. **Type Safety**: Use TypeScript with proper type definitions throughout the codebase. 5. **Comprehensive Documentation**: Document all components, interfaces, and extension points. ## Project Structure Organize your MCP server using this structure: ``` mcp-server/ ├── src/ │ ├── services/ # Service classes for API interactions │ │ ├── base-service.ts # Abstract base service with common functionality │ │ └── specific-service.ts # Concrete service implementations │ ├── tools/ # MCP tool definitions and handlers │ │ ├── tool-definitions.ts # Tool schemas (name, description, inputSchema) │ │ └── tool-handlers.ts # Tool implementation logic │ ├── types/ # TypeScript type definitions │ │ └── domain-types.ts # Domain-specific type definitions │ ├── config.ts # Configuration management │ └── index.ts # Main entry point ├── .env.example # Example environment variables ├── package.json # Project dependencies and scripts ├── tsconfig.json # TypeScript configuration └── README.md # Project documentation ``` ## Step-by-Step Implementation Guide ### 1. Set Up Project Structure Begin by creating the directory structure and configuration files: ```bash mkdir -p src/{services,tools,types} touch src/config.ts src/index.ts ``` ### 2. Define Configuration Management Create a centralized configuration system in `config.ts` that: - Loads environment variables - Provides type-safe access to configuration - Validates required settings - Handles command-line arguments Example: ```typescript // src/config.ts import dotenv from 'dotenv'; // Load environment variables dotenv.config(); export interface Config { apiKey: string; serviceUrl: string; // Add other configuration properties } // Create and validate configuration const config: Config = { apiKey: process.env.API_KEY || '', serviceUrl: process.env.SERVICE_URL || 'https://api.example.com', // Add other properties }; // Validate required fields const missingEnvVars = Object.entries(config) .filter(([key, value]) => { // Check required fields if (key === 'apiKey') return !value; return false; }) .map(([key]) => key); if (missingEnvVars.length > 0) { throw new Error(`Missing required environment variables: ${missingEnvVars.join(', ')}`); } export default config; ``` ### 3. Create Base Service Class Implement a base service class that handles common functionality: - HTTP requests with axios - Error handling - Rate limiting - Logging Example: ```typescript // src/services/base-service.ts import axios, { AxiosInstance } from 'axios'; export abstract class BaseService { protected client: AxiosInstance; constructor(baseURL: string, headers: Record<string, string>) { this.client = axios.create({ baseURL, headers }); // Add interceptors for error handling, rate limiting, etc. } // Common methods for API requests, error handling, etc. } ``` ### 4. Implement Domain-Specific Services Create service classes that extend the base service and implement domain-specific functionality: ```typescript // src/services/specific-service.ts import { BaseService } from './base-service.js'; import config from '../config.js'; import { ResourceType } from '../types/domain-types.js'; export class SpecificService extends BaseService { // Implement singleton pattern if needed private static instance: SpecificService; private constructor() { super(config.serviceUrl, { 'Authorization': `Bearer ${config.apiKey}`, 'Content-Type': 'application/json' }); } public static getInstance(): SpecificService { if (!SpecificService.instance) { SpecificService.instance = new SpecificService(); } return SpecificService.instance; } // Implement domain-specific methods async getResources(): Promise<ResourceType[]> { // Implementation } // Other methods... } ``` ### 5. Define Type Definitions Create clear type definitions for your domain objects: ```typescript // src/types/domain-types.ts export interface ResourceType { id: string; name: string; // Other properties } export interface CreateResourceData { name: string; // Other properties } // Other type definitions... ``` ### 6. Define MCP Tools Define the tools that your MCP server will expose: ```typescript // src/tools/tool-definitions.ts export const tools = [ { name: "get_resources", description: "Retrieve a list of resources", inputSchema: { type: "object", properties: { // Define input properties } } }, // Other tool definitions... ]; // Define handler function type export type ToolHandler = (args: any) => Promise<any>; // Map tool names to handlers export interface ToolHandlers { [toolName: string]: ToolHandler; } ``` ### 7. Implement Tool Handlers Create handlers for each tool that process arguments and call service methods: ```typescript // src/tools/tool-handlers.ts import { SpecificService } from '../services/specific-service.js'; import { ToolHandlers } from './tool-definitions.js'; export function createToolHandlers(service: SpecificService): ToolHandlers { return { get_resources: async (args: any) => { try { const resources = await service.getResources(); return { resources }; } catch (error) { console.error('Error in get_resources:', error); throw new Error(`Failed to get resources: ${error instanceof Error ? error.message : String(error)}`); } }, // Other handlers... }; } ``` ### 8. Create Main Entry Point Implement the main entry point that initializes services, registers tools, and starts the server: ```typescript // src/index.ts import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; import { SpecificService } from "./services/specific-service.js"; import { tools } from "./tools/tool-definitions.js"; import { createToolHandlers } from "./tools/tool-handlers.js"; async function main() { try { // Initialize services const service = SpecificService.getInstance(); // Create tool handlers const toolHandlers = createToolHandlers(service); // Create MCP server const server = new Server( { name: "your-mcp-server", version: "0.1.0", }, { capabilities: { tools: {}, } } ); // Register tool list handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); // Register tool call handler server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const toolName = request.params.name; const handler = toolHandlers[toolName]; if (!handler) { throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${toolName}` ); } const result = await handler(request.params.arguments); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } catch (error) { // Error handling } }); // Connect to transport const transport = new StdioServerTransport(); await server.connect(transport); console.log("MCP Server running on stdio"); } catch (error) { console.error("Failed to start server:", error); process.exit(1); } } main().catch(console.error); ``` ## Best Practices for MCP Server Development ### Modularity Guidelines 1. **Single Responsibility Principle**: Each file should have a single, well-defined purpose. 2. **Interface Segregation**: Define clear interfaces between components. 3. **Dependency Injection**: Pass dependencies to components rather than creating them internally. 4. **Separation of Concerns**: Keep tool definitions separate from their implementations. ### File Size and Organization 1. **Keep Files Small**: Aim for files under 300 lines of code. 2. **Logical Grouping**: Group related functionality in the same directory. 3. **Consistent Naming**: Use a consistent naming convention for files and directories. 4. **Export Patterns**: Be explicit about what each module exports. ### Error Handling 1. **Comprehensive Error Handling**: Handle errors at all levels (service, tool, server). 2. **Informative Error Messages**: Provide clear error messages that help diagnose issues. 3. **Error Propagation**: Properly propagate errors up the call stack. 4. **Logging**: Log errors with appropriate context for debugging. ### Documentation 1. **JSDoc Comments**: Document all classes, methods, and interfaces. 2. **README**: Provide comprehensive documentation on how to use and extend the server. 3. **Code Examples**: Include examples of how to add new tools and services. 4. **Architecture Overview**: Document the overall architecture and design decisions. ## Extension Patterns When extending the MCP server with new functionality, follow these patterns: ### Adding a New Service 1. Create a new service class in `src/services/`. 2. Extend the base service class. 3. Implement domain-specific methods. 4. Add any necessary type definitions in `src/types/`. ### Adding New Tools 1. Define new tools in `src/tools/tool-definitions.ts`. 2. Implement handlers in `src/tools/tool-handlers.ts`. 3. Register the tools and handlers in `src/index.ts`. ## Testing Your MCP Server To test your MCP server: 1. Build the project: `npm run build` 2. Run the server: `npm start` 3. Test with an MCP client or using the MCP CLI tool. ## Common Pitfalls to Avoid 1. **Monolithic Files**: Avoid putting too much functionality in a single file. 2. **Tight Coupling**: Avoid tight coupling between components. 3. **Inconsistent Error Handling**: Ensure consistent error handling throughout the codebase. 4. **Missing Documentation**: Document all components and extension points. 5. **Hardcoded Configuration**: Use the configuration system instead of hardcoding values. By following these guidelines, you will create an MCP server that is modular, maintainable, and easy to extend.